Системное программирование

Тема 7. Механизмы управления виртуальной и динамически распределяемой памятью

Системное программирование

План лекции

1. Понятие динамически подключаемой библиотеки

2. Структура DLL-библиотеки

3. Создание DLL-библиотеки

4. Проецирование файлов на виртуальное адресное пространство

5. Разработка и использование динамически загружаемых модулей

6. Обмен данными между процессами с использованием динамически загружаемых модулей и разделяемых сегментов памяти

7. Создание многозадачных комплексов

8. Перехват системных вызовов (hooking)

Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

1. Динамически подключаемые библиотеки

1.1. Понятие DLL

DLL (Dynamic Link Library) — библиотека, загружаемая во время выполнения.

Преимущества:

  • Экономия памяти (одна копия для всех процессов)
  • Модульность приложений
  • Обновление без перекомпиляции
  • Поддержка плагинов
  • Расширяемость

Сравнение:

Характеристика Статическая Динамическая
Размер EXE Большой Меньший
Загрузка При запуске При запуске или по требованию
Обновление Перекомпиляция Замена DLL
Использование памяти Копия на процесс Разделяемая
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

1.2. Типы связывания

Неявное (Implicit) связывание:

// При компиляции линкуется .lib файл
#pragma comment(lib, "mylib.lib")

// Использование функций
void MyFunction();  // Объявление
MyFunction();       // Вызов

Явное (Explicit) связывание:

// Загрузка во время выполнения
HMODULE hModule = LoadLibrary(L"mylib.dll");
FARPROC proc = GetProcAddress(hModule, "MyFunction");
((void(*)())proc)();
FreeLibrary(hModule);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

2. Структура DLL

2.1. Экспорт функций

// mylib.h
#ifdef MYLIB_EXPORTS
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif

// Объявление функции
MYLIB_API int Add(int a, int b);
MYLIB_API void ProcessData(const char* data);

// Экспорт класса
class MYLIB_API MyClass {
public:
    void DoSomething();
};
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

2.2. Файл определения модуля (.def)

; mylib.def
LIBRARY MYLIB
EXPORTS
    Add         @1
    Subtract    @2
    ProcessData @3 NONAME
    
    ; Экспорт по порядковому номеру
    Multiply    @4
    
    ; Экспорт с псевдонимом
    Div = Divide @5

Преимущества .def файла:

  • Экспорт по порядковому номеру (быстрее)
  • Скрытие имен функций (NONAME)
  • Псевдонимы функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

3. Создание DLL

3.1. Точка входа DLL

// dllmain.cpp
#include <windows.h>

BOOL APIENTRY DllMain(
    HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
) {
    switch (ul_reason_for_call) {
        case DLL_PROCESS_ATTACH:
            // Процесс загрузил DLL
            // Инициализация
            break;
            
        case DLL_THREAD_ATTACH:
            // Создан новый поток
            break;
        case DLL_THREAD_DETACH:
            // Поток завершился
            break;
            
        case DLL_PROCESS_DETACH:
            // Процесс выгружает DLL
            // Освобождение ресурсов
            break;
    }
    return TRUE;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

3.2. Полный пример DLL

// mathlib.h
#ifdef MATHLIB_EXPORTS
#define MATHLIB_API __declspec(dllexport)
#else
#define MATHLIB_API __declspec(dllimport)
#endif

extern "C" {
    MATHLIB_API double CalculateCircleArea(double radius);
    MATHLIB_API double CalculateRectangleArea(double w, double h);
}

// mathlib.cpp
#include "mathlib.h"
#include <cmath>

MATHLIB_API double CalculateCircleArea(double radius) {
    return M_PI * radius * radius;
}

MATHLIB_API double CalculateRectangleArea(double w, double h) {
    return w * h;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

4. Проецирование файлов

4.1. Memory-Mapped Files

Проецирование файла — отображение файла на адресное пространство процесса.

Преимущества:

  • Эффективная работа с большими файлами
  • Разделение данных между процессами
  • Отсутствие явного чтения/записи
  • Кэширование ОС

Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

4.2. Создание проекции в Windows

// 1. Открытие или создание файла
HANDLE hFile = CreateFile(
    L"data.bin",
    GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_ALWAYS,
    FILE_ATTRIBUTE_NORMAL, NULL
);

// 2. Создание объекта проекции
HANDLE hMapping = CreateFileMapping(
    hFile, NULL,
    PAGE_READWRITE,
    0, 1024 * 1024,  // Размер: 1 МБ
    L"MyMapping"
);
// 3. Проецирование в адресное пространство
LPVOID pView = MapViewOfFile(
    hMapping, FILE_MAP_ALL_ACCESS,
    0, 0, 0  // Весь файл
);

// 4. Использование
strcpy((char*)pView, "Hello, Memory Mapping!");

// 5. Освобождение
UnmapViewOfFile(pView);
CloseHandle(hMapping);
CloseHandle(hFile);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

4.3. Проецирование в POSIX

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

// 1. Открытие файла
int fd = open("data.bin", O_RDWR | O_CREAT, 0666);

// 2. Установка размера
ftruncate(fd, 1024 * 1024);

// 3. Проецирование
void* addr = mmap(
    NULL,           // Адрес (NULL = выбор системы)
    1024 * 1024,     // Размер
    PROT_READ | PROT_WRITE,  // Права доступа
    MAP_SHARED,     // Разделяемое отображение
    fd,             // Файловый дескриптор
    0               // Смещение
);

// 4. Использование
sprintf((char*)addr, "Hello, mmap!");

// 5. Освобождение
munmap(addr, 1024 * 1024);
close(fd);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

5. Использование DLL

5.1. Неявное связывание

// Подключение библиотеки импорта
#pragma comment(lib, "mathlib.lib")

// Объявление функций
extern "C" __declspec(dllimport) 
double CalculateCircleArea(double radius);

// Использование
int main() {
    double area = CalculateCircleArea(5.0);
    printf("Area: %f\n", area);
    return 0;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

5.2. Явное связывание

#include <windows.h>
#include <stdio.h>

typedef double (*CALC_CIRCLE_AREA)(double);

int main() {
    // Загрузка DLL
    HMODULE hModule = LoadLibrary(L"mathlib.dll");
    if (!hModule) {
        printf("Failed to load DLL\n");
        return 1;
    }
    
    // Получение адреса функции
    CALC_CIRCLE_AREA calcArea = (CALC_CIRCLE_AREA)
        GetProcAddress(hModule, "CalculateCircleArea");
    
    if (!calcArea) {
        printf("Failed to get function\n");
        FreeLibrary(hModule);
        return 1;
    }
    
    // Вызов
    double area = calcArea(5.0);
    printf("Area: %f\n", area);
    
    // Выгрузка
    FreeLibrary(hModule);
    return 0;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

6. Обмен данными между процессами

6.1. Разделяемая память

Windows:

// Процесс 1 (создание)
HANDLE hMap = CreateFileMapping(
    INVALID_HANDLE_VALUE,  // Системный файл подкачки
    NULL, PAGE_READWRITE,
    0, 4096, L"SharedMemory"
);
void* pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 4096);
strcpy((char*)pBuf, "Hello from Process 1");

// Процесс 2 (подключение)
HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"SharedMemory");
void* pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
printf("Received: %s\n", (char*)pBuf);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

6.2. Разделяемая память в POSIX

// Процесс 1
int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 4096);
void* addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, 
                  MAP_SHARED, fd, 0);
sprintf((char*)addr, "Hello from Process 1");

// Процесс 2
int fd = shm_open("/myshm", O_RDWR, 0666);
void* addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);
printf("Received: %s\n", (char*)addr);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

6.3. Синхронизация доступа

Проблема: Несколько процессов одновременно изменяют данные.

Решение — именованные мьютексы:

// Windows
HANDLE hMutex = CreateMutex(NULL, FALSE, L"SharedMutex");
WaitForSingleObject(hMutex, INFINITE);
// Работа с разделяемой памятью
ReleaseMutex(hMutex);

// POSIX
sem_t* sem = sem_open("/mysem", O_CREAT, 0666, 1);
sem_wait(sem);
// Работа с разделяемой памятью
sem_post(sem);
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

7. Создание многозадачных комплексов

7.1. Архитектура многозадачного приложения

Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

7.2. Паттерн "Мастер-Рабочие"

// Мастер-процесс
void MasterProcess() {
    // Создание разделяемой памяти
    HANDLE hShm = CreateFileMapping(...);
    void* pData = MapViewOfFile(...);
    
    // Создание рабочих процессов
    for (int i = 0; i < numWorkers; i++) {
        STARTUPINFO si = {sizeof(si)};
        PROCESS_INFORMATION pi;
        
        wchar_t cmdLine[256];
        swprintf(cmdLine, L"worker.exe %d", i);
        
        CreateProcess(NULL, cmdLine, ...);
    }
    
    // Распределение задач
    for (int i = 0; i < numTasks; i++) {
        // Запись задачи в разделяемую память
        // Сигнализация семафором
    }
    
    // Сбор результатов
    WaitForMultipleObjects(numWorkers, hWorkers, TRUE, INFINITE);
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

7.3. Паттерн "Производитель-Потребитель"

// Производитель
void Producer() {
    while (hasData) {
        sem_wait(&empty);   // Ждать свободное место
        sem_wait(&mutex);   // Захватить доступ
        
        buffer[in] = produce();
        in = (in + 1) % BUFFER_SIZE;
        
        sem_post(&mutex);   // Освободить доступ
        sem_post(&full);    // Уведомить о данных
    }
}
// Потребитель
void Consumer() {
    while (running) {
        sem_wait(&full);    // Ждать данные
        sem_wait(&mutex);   // Захватить доступ
        
        item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        
        sem_post(&mutex);   // Освободить доступ
        sem_post(&empty);   // Уведомить о свободном месте
        
        consume(item);
    }
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8. Перехват системных вызовов

8.1. Понятие hooking (перехвата)

Hooking — техника перехвата вызовов функций для модификации поведения программы.

Применение:

  • Отладка и мониторинг
  • Модификация поведения
  • Антивирусное ПО
  • Инструменты тестирования
  • Реверс-инжиниринг

Типы hooking:

Тип Описание Сложность
IAT Hooking Перехват через таблицу импорта Низкая
Inline Hooking Перезапись кода функции Средняя
VTable Hooking Перехват виртуальных методов Средняя
DLL Injection Внедрение кода в процесс Средняя
Kernel Hooking Перехват на уровне ядра Высокая
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.2. IAT Hooking (Windows)

Import Address Table — таблица адресов импортируемых функций.

Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Пример IAT Hooking:

#include <windows.h>
#include <iostream>

// Тип оригинальной функции
typedef int (WINAPI* MessageBoxAPtr)(HWND, LPCSTR, LPCSTR, UINT);

MessageBoxAPtr OriginalMessageBoxA = nullptr;

// Наша функция-перехватчик
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText, 
                              LPCSTR lpCaption, UINT uType) {
    std::cout << "MessageBoxA called: " << lpText << std::endl;
    // Можем изменить параметры или заблокировать вызов
    return OriginalMessageBoxA(hWnd, "Hooked!", lpCaption, uType);
}

// Перехват IAT
void HookIAT(const char* moduleName, const char* funcName, void* newFunc) {
    HMODULE hModule = GetModuleHandleA(NULL);
    IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)hModule;
    IMAGE_NT_HEADERS* ntHeaders = (IMAGE_NT_HEADERS*)((BYTE*)hModule + 
                                                        dosHeader->e_lfanew);
    
    IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)(
        (BYTE*)hModule + ntHeaders->OptionalHeader.DataDirectory[
            IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    
    while (importDesc->Name) {
        const char* dllName = (const char*)((BYTE*)hModule + importDesc->Name);
        
        if (_stricmp(dllName, moduleName) == 0) {
            IMAGE_THUNK_DATA* thunk = (IMAGE_THUNK_DATA*)(
                (BYTE*)hModule + importDesc->FirstThunk);
            while (thunk->u1.Function) {
                FARPROC* funcPtr = (FARPROC*)&thunk->u1.Function;
                
                if (*funcPtr == GetProcAddress(
                        GetModuleHandleA(moduleName), funcName)) {
                    
                    DWORD oldProtect;
                    VirtualProtect(funcPtr, sizeof(void*), 
                                   PAGE_EXECUTE_READWRITE, &oldProtect);
                    
                    OriginalMessageBoxA = (MessageBoxAPtr)*funcPtr;
                    *funcPtr = (FARPROC)newFunc;
                    
                    VirtualProtect(funcPtr, sizeof(void*), 
                                   oldProtect, &oldProtect);
                }
                thunk++;
            }
        }
        importDesc++;
    }
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.3. LD_PRELOAD (Linux)

LD_PRELOAD — переменная окружения для загрузки библиотек до других.

Пример перехвата malloc:

// hook.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

static void* (*real_malloc)(size_t) = NULL;

void* malloc(size_t size) {
    if (!real_malloc) {
        real_malloc = dlsym(RTLD_NEXT, "malloc");
    }
    
    printf("malloc(%zu) called\n", size);
    return real_malloc(size);
}

void free(void* ptr) {
    static void (*real_free)(void*) = NULL;
    if (!real_free) {
        real_free = dlsym(RTLD_NEXT, "free");
    }
    
    printf("free(%p) called\n", ptr);
    real_free(ptr);
}

Компиляция и использование:

gcc -shared -fPIC -o hook.so hook.c -ldl
LD_PRELOAD=./hook.so ./program
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.4. ptrace (Linux)

ptrace — системный вызов для отладки и трассировки процессов.

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/user.h>

int main() {
    pid_t child = fork();
    
    if (child == 0) {
        // Дочерний процесс: разрешаем трассировку
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("./target", "target", NULL);
    } else {
        // Родительский процесс: трассируем
        int status;
        waitpid(child, &status, 0);
        while (WIFSTOPPED(status)) {
            struct user_regs_struct regs;
            ptrace(PTRACE_GETREGS, child, NULL, &regs);
            
            printf("Syscall: %lld\n", regs.orig_rax);
            
            // Продолжить до следующего syscall
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
            waitpid(child, &status, 0);
        }
    }
    
    return 0;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.5. Microsoft Detours

Detours — библиотека Microsoft для hooking на уровне кода.

#include <detours.h>
#include <windows.h>

static int (WINAPI* TrueMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT) = MessageBoxA;

int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText, 
                              LPCSTR lpCaption, UINT uType) {
    return TrueMessageBoxA(hWnd, "Detoured!", lpCaption, uType);
}

int main() {
    // Установка hook
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
    DetourTransactionCommit();
    
    MessageBoxA(NULL, "Test", "Test", MB_OK);
    
    // Снятие hook
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourDetach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
    DetourTransactionCommit();
    
    return 0;
}
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.6. eBPF (Linux)

eBPF (Extended Berkeley Packet Filter) — технология выполнения песочницы программ в ядре Linux без необходимости написания модулей ядра.

Архитектура eBPF:

Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Ключевые компоненты:

  • Verifier — статический анализатор, проверяющий безопасность программы (отсутствие бесконечных циклов, доступ только к разрешённой памяти, ограниченное число инструкций)
  • JIT-компиляция — преобразование BPF-байткода в нативный машинный код для максимальной производительности
  • BPF Maps — структуры данных для обмена между ядром и пользовательским пространством (hash, array, ring buffer, per-CPU)
  • Helper functions — набор разрешённых функций ядра, которые eBPF-программа может вызывать (bpf_get_current_pid_tgid, bpf_probe_read, bpf_trace_printk и др.)
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Типы eBPF-программ:

Тип Точка прикрепления Применение
kprobes/kretprobes Точка входа/выхода функции ядра Отладка, мониторинг
tracepoints Статические точки трассировки ядра Мониторинг событий
XDP (eXpress Data Path) Драйвер сетевой карты Высокоскоростная обработка пакетов
tc (traffic control) Сетевой стек Linux Фильтрация, QoS
perf_events Счётчики производительности Профилирование
uprobe/uretprobe Точка входа/выхода функции пользователя Трассировка приложений
cgroupskb Контрольная группа Мониторинг контейнеров
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Пример: трассировка open() с помощью bpftrace (однострочник):

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s: %s\n", comm, str(args->filename)); }'
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Пример: BPF-программа с BCC (Python + BPF C):

from bcc import BPF

bpf_text = """
#include <uapi/linux/ptrace.h>

struct data_t {
    u32 pid;
    char comm[16];
    char fname[256];
};

BPF_PERF_OUTPUT(events);

int trace_open(struct pt_regs *ctx, const char __user *filename) {
    struct data_t data = {};
    u32 pid = bpf_get_current_pid_tgid() >> 32;

    data.pid = pid;
    bpf_get_current_comm(&data.comm, sizeof(data.comm));
    bpf_probe_read_user_str(&data.fname, sizeof(data.fname), filename);

    events.perf_submit(ctx, &data, sizeof(data));
    return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event="do_sys_openat2", fn_name="trace_open")

def print_event(cpu, data, size):
    event = b["events"].event(data)
    print(f"{event.comm.decode()} [{event.pid}] {event.fname.decode()}")

b["events"].open_perf_buffer(print_event)

while True:
    b.perf_buffer_poll()
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Инструменты на базе eBPF:

Инструмент Назначение
bpftrace Высокоуровневый язык трассировки (однострочники, скрипты)
BCC (BPF Compiler Collection) Python/C-фреймворк для eBPF-инструментов
Cilium CNI-плагин для Kubernetes с eBPF-сетевой политикой
Falco Обнаружение аномалий в рантайме (безопасность)
bpftrace-tools Набор готовых инструментов (execsnoop, opensnoop, biosnoop)
Katran L4-балансировщик нагрузки от Facebook
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Пример: tracing open() syscalls (libbpf, eBPF C):

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

struct event {
    u32 pid;
    char comm[16];
    char fname[256];
};

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 1 << 24);
} events SEC(".maps");

SEC("tracepoint/syscalls/sys_enter_openat")
int trace_openat(struct trace_event_raw_sys_enter* ctx) {
    struct event* e;

    e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
    if (!e) return 0;

    e->pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&e->comm, sizeof(e->comm));
    bpf_probe_read_user_str(&e->fname, sizeof(e->fname),
                             (const char*)ctx->args[1]);

    bpf_ringbuf_submit(e, 0);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Сравнение методов мониторинга/перехвата:

Характеристика eBPF ptrace LD_PRELOAD
Уровень Ядро Ядро (system call) Пользовательский
Производительность Очень высокая Низкая (context switch) Высокая
Область видимости Все процессы Один процесс Один процесс
Требование root Да (для большинства) Нет (свой процесс) Нет
Безопасность Verifier, sandbox SIGSTOP/SIGCONT Обход через статическую линковку
Гибкость Ограничена verifier Полная Полная
Изменение данных Ограничено Да Да
Использование Мониторинг, сеть, безопасность Отладчики, strace Перехват библиотечных функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

8.7. Сравнение методов hooking

Метод Платформа Уровень Обнаруживаемость Сложность
IAT Hooking Windows User Высокая Низкая
Inline Hooking Любая User Средняя Средняя
LD_PRELOAD Linux User Высокая Низкая
ptrace Linux User Высокая Средняя
Detours Windows User Низкая Низкая
eBPF Linux Kernel Низкая Высокая

Рекомендации:

  • Отладка/мониторинг: LD_PRELOAD, ptrace, eBPF
  • Модификация ПО: Detours, IAT Hooking
  • Безопасность: eBPF, kernel modules
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Резюме

Ключевые моменты лекции:

  1. DLL — динамически подключаемые библиотеки
  2. Экспорт/Импорт — механизмы использования DLL
  3. Проецирование файлов — эффективная работа с памятью
  4. Разделяемая память — IPC через общую память
  5. Синхронизация — защита разделяемых данных
  6. Многозадачность — паттерны распределения работы
  7. Hooking — перехват системных вызовов
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Вопросы для самопроверки

  1. Чем отличается неявное связывание от явного?
  2. Какие функции выполняет DllMain?
  3. Что такое проецирование файла?
  4. Как организовать обмен данными между процессами?
  5. Какие паттерны многозадачности существуют?
  6. Как синхронизировать доступ к разделяемой памяти?
  7. Что такое IAT Hooking?
  8. Как работает LD_PRELOAD?
  9. Что такое eBPF и для чего он используется?
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Практические задания

Самостоятельная работа (6 часов):

  1. Создать простую DLL с экспортируемыми функциями
  2. Написать программу с явной загрузкой DLL
  3. Организовать разделяемую память между процессами
  4. Исследовать LD_PRELOAD для перехвата функций
Механизмы управления виртуальной и динамически распределяемой памятью
Системное программирование

Рекомендуемая литература

Основная:

  1. Таненбаум, Э. Современные операционные системы. — 4-е изд. — СПб.: Питер, 2021.
  2. Kerrisk, M. The Linux Programming Interface. — No Starch Press, 2010.

Дополнительная:

  1. MSDN: Dynamic-Link Libraries, File Mapping, Detours
  2. man pages: dlopen(3), mmap(2), shm_overview(7), ptrace(2)
  3. eBPF Documentation: https://ebpf.io/
  4. Microsoft Detours: https://github.com/microsoft/Detours
Механизмы управления виртуальной и динамически распределяемой памятью

Обозначить 8 пунктов плана, дать ориентир по времени (~90 мин). Упомянуть, что тема связана с предыдущей лекцией по памяти и подготовит к лабораторным работам по IPC.

Спросить студентов, какие DLL они знают (kernel32.dll, ntdll.dll). Обратить внимание на сравнительную таблицу — попросить привести примеры из практики, когда перекомпиляция при обновлении библиотеки была бы проблемой.

Ключевое различие: при неявном линкер сам подтягивает DLL при запуске, при явном — программист контролирует загрузку вручную. Спросить, когда какой подход предпочтительнее (ответ: явное — для плагинов и опциональных модулей). Показать аналог в Linux: dlopen/dlsym.

Макрос MYLIB_EXPORTS определяется при сборке самой DLL (через /D в командной строке компилятора). Важно: при экспорте функций C++ происходит декорирование имён (name mangling) — поэтому для совместимости используют extern "C".

.def файл — устаревший, но всё ещё полезный механизм. Обратить внимание на экспорт по порядковому номеру (@1, @2) — это быстрее, чем поиск по имени, но менее безопасно при обновлении. Псевдонимы (Div = Divide) полезны для переименования без изменения клиентского кода.

DllMain вызывается для каждого процесса и потока — подчеркнуть, что нельзя делать в DllMain: вызывать LoadLibrary, ждать других потоков, использовать синхронизацию (возможен дедлок). Частая ошибка — тяжёлая инициализация в DLL_PROCESS_ATTACH.

Показать полный цикл: заголовочный файл, реализация, сборка DLL через cl /LD. extern "C" критичен для избежания name mangling. Предложить студентам поэкспериментировать с dumpbin /exports для просмотра экспортируемых функций.

Связать с темой виртуальной памяти: проецирование — это создание отображения виртуальных страниц на физические страницы, содержащие данные файла. Задать вопрос: что произойдёт при записи в проекцию с PAGE_READONLY? Спросить, где ещё используется mmap (ответ: загрузка executables, shared libraries).

Разобрать каждый шаг по порядку. Важно: CreateFileMapping с hFile=INVALID_HANDLE_VALUE создаёт анонимную проекцию (shared memory). Напомнить про необходимость CloseHandle для всех объектов. Показать, как Process Explorer показывает секции (mapped views).

Сравнить с Windows-версией: mmap вместо CreateFileMapping+MapViewOfFile, shm_open для анонимной разделяемой памяти. MAP_SHARED vs MAP_PRIVATE — ключевое различие: при MAP_PRIVATE изменения не видны другим процессам (copy-on-write). Напомнить про необходимость msync() перед munmap для гарантии записи на диск.

Простой пример — переход от явного к неявному связыванию. Показать, что .lib файл генерируется при сборке DLL. Упомянуть проблему «DLL hell» — что бывает, когда версия DLL не совпадает (решение: side-by-side assemblies, manifests).

Важно показать typedef для указателя на функцию и проверку ошибок после каждого вызова. Спросить: когда лучше использовать явное связывание? (плагины, опциональный функционал, когда DLL может отсутствовать).

INVALID_HANDLE_VALUE — ключевая деталь: означает, что проекция опирается на файл подкачки, а не на реальный файл. Создание vs открытие: CreateFileMapping — первый процесс создаёт, OpenFileMapping — остальные подключаются. Обратить внимание на необходимость одинакового размера.

shm_open — создаёт файл в /dev/shm (tmpfs). Показать через ls /dev/shm. Обратить внимание, что shm_unlink удаляет имя, но память остаётся, пока не закроются все дескрипторы. Спросить: чем отличается shm_open от обычного open + mmap?

Это критический слайд — связать с лекцией по синхронизации. Задать вопрос: что будет, если два процесса одновременно пишут в разделяемую память без синхронизации? (data race, неопределённое поведение). Именованные мьютексы/семафоры нужны именно потому, что обычные мьютексы работают только внутри одного процесса.

Визуализировать архитектуру на доске: главный процесс как координатор, рабочие как исполнители. Спросить: какие реальные системы используют такую архитектуру? (ответ: компиляторы с параллельной компиляцией, веб-серверы worker processes, база данных PostgreSQL).

Разобрать код: CreateProcess для запуска рабочих, разделяемая память для передачи задач, WaitForMultipleObjects для ожидания завершения. Спросить: в чём разница между процессами и потоками для этой задачи? (процессы изолированы, но дороже в создании; потоки делят адресное пространство, проще обмен данными).

Классический паттерн — разбор на доске с буфером и указателями in/out. Важно: порядок sem_wait — сначала empty/full, потом mutex (иначе дедлок!). Спросить: как изменится код, если буфер кольцевой и одного элемента?

Большой слайд — выделить на него больше времени (~15 мин). Hooking — двоякая технология: используется как в легитимных целях (антивирусы, отладчики), так и в вредоносных (rootkits, кейлоггеры). Спросить: как антивирус обнаруживает IAT hooking?

Быстро пробежаться по 7 пунктам. Предложить студентам выбрать 2-3 ключевых темы для самостоятельного углубления.

Выбрать 2-3 вопроса для обсуждения в аудитории. Вопросы 4 и 6 особенно важны — они связывают тему с предыдущими лекциями по IPC и синхронизации.

Задания 1-2 выполнить на Windows (Visual Studio), задание 3 — на Linux (POSIX shared memory), задание 4 — на Linux (LD_PRELOAD). Рекомендовать начать с LD_PRELOAD — это самый наглядный и быстрый результат.

Таненбаум — глава по управлению памятью. Kerrisk — главы 49 (разделяемая память), 14 (mmap). Для hooking: man ptrace(2), документация eBPF на ebpf.io.